const {
    get_file_list,
    get_full_path,
    format_bytes,
} = require("../utils/file_tool");
const { success, getPrint, info, warn, whisper } = require("../utils/log_tool");
const { BaseCommand } = require("./base");
const path = require("node:path");
const { progress } = require("../utils/display_tool");
const { createWriteStream, readFileSync } = require("node:fs");
const { getCache, setCache } = require("../utils/cache_tool");
const crypto = require("node:crypto");

class TencentCosCommand extends BaseCommand {
    async execute() {
        const config = this.selfData.config;
        const mode = this.selfData.mode;

        return await new Promise((resolve, reject) => {
            // 引入模块
            var COS = require("cos-nodejs-sdk-v5");
            // 创建实例
            var cos = new COS({
                SecretId: config.secret_id,
                SecretKey: config.secret_key,
            });

            // 存储桶名称，由bucketname-appid 组成，appid必须填入，可以在COS控制台查看存储桶名称。 https://console.cloud.tencent.com/cos5/bucket
            var Bucket = `${config.bucket}-${config.appid}`;
            // 存储桶Region可以在COS控制台指定存储桶的概览页查看 https://console.cloud.tencent.com/cos5/bucket/
            // 关于地域的详情见 https://cloud.tencent.com/document/product/436/6224
            var Region = config.region;

            switch (mode) {
                case "VIEW": {
                    const prefix = this.selfData.src || "";
                    const keys = [];
                    /**
                     *
                     * @param {COS.GetBucketResult} ret
                     */
                    function getPartsComplete(data) {
                        keys.push(...data.Contents.map((v) => v.Key));
                        progress(keys.length, 0, {
                            desc: data.NextMarker || "[FINISH]",
                            depth: 0,
                            color: "gray",
                        });
                        if (data.NextMarker) {
                            getParts(data.NextMarker);
                            return;
                        }
                        info("");
                        resolve(keys);
                    }

                    function getParts(marker = null) {
                        cos.getBucket(
                            {
                                Bucket: Bucket,
                                Region: Region,
                                MaxKeys: 1000,
                                Marker: marker,
                                Prefix: prefix || "",
                            },
                            (err, data) => {
                                if (err) {
                                    reject(getPrint(err, true));
                                    return;
                                }
                                getPartsComplete(data);
                            },
                        );
                    }
                    getParts();
                    break;
                }
                case "DOWNLOAD": {
                    const src = this.selfData.src;
                    const dst = get_full_path(this.selfData.dst);
                    cos.getObject(
                        {
                            Bucket: Bucket,
                            Region: Region,
                            Key: src,
                            onProgress: (progressData) => {
                                progress(
                                    progressData.loaded,
                                    progressData.total,
                                    {
                                        desc: `Downloading from ${src}  ${format_bytes(progressData.speed)}/s`,
                                        depth: 0,
                                        color: "cyan",
                                        align: true,
                                        format_bytes: true,
                                    },
                                    true,
                                );
                            },
                            Output: createWriteStream(dst),
                        },
                        (err, data) => {
                            if (err) {
                                reject(getPrint(err, true));
                                return;
                            }
                            resolve(data);
                        },
                    );
                    break;
                }
                case "UPLOAD_FILE":
                case "UPLOAD": {
                    const dst = this.selfData.dst;
                    const src = get_full_path(this.selfData.src);
                    info("");
                    info(`开始上传 ${src} 到 ${dst}`);
                    // 分片上传
                    cos.sliceUploadFile(
                        {
                            Bucket: Bucket,
                            Region: Region,
                            Key: dst,
                            onProgress: (progressInfo) => {
                                progress(
                                    progressInfo.loaded,
                                    progressInfo.total,
                                    {
                                        desc: `Uploading to ${dst} ${format_bytes(progressInfo.speed)}/s`,
                                        depth: 0,
                                        color: "green",
                                        format_bytes: true,
                                        align: true,
                                    },
                                    true,
                                );
                            },
                            FilePath: src, // 本地文件地址，需自行替换
                        },
                        (err, data) => {
                            if (err) {
                                reject(getPrint(err, true));
                                return;
                            }
                            resolve(data);
                        },
                    );
                    break;
                }
                case "UPLOAD_FILES": {
                    const dst_folder = this.selfData.dst;
                    const strategy = this.selfData.options?.strategy || "lazy";
                    const concurrency =
                        this.selfData.options?.concurrency || 10;
                    const base = this.selfData.base;
                    whisper(
                        `在 ${base} 中收集要上传的文件...`,
                        undefined,
                        true,
                    );
                    let srcs = get_file_list(
                        this.selfData.src,
                        undefined,
                        true,
                    );
                    const cacheKey =
                        `tencent_cos/${Bucket}/${dst_folder}`.replace(
                            /\//g,
                            ".",
                        );
                    const cacheType = this.selfData.cache_type;
                    const cacheParam = this.selfData.cache_param;
                    let cachedFiles = null;
                    getCache(cacheKey, cacheType, cacheParam).then(
                        (_cachedFiles) => {
                            cachedFiles = _cachedFiles;
                            info(
                                `cacheKey=${cacheKey}, cacheType=${cacheType}, cacheParam=${cacheParam},cachedFiles=${JSON.stringify(cachedFiles).substring(0, 20)}`,
                            );
                            if (
                                !cachedFiles ||
                                !Object.keys(cachedFiles).length
                            ) {
                                const prefix = dst_folder;
                                const keys = {};
                                /**
                                 *
                                 * @param {COS.GetBucketResult} data
                                 */
                                function getPartsComplete(data) {
                                    data.Contents.forEach((obj) => {
                                        if (!keys[obj.Key]) {
                                            keys[obj.Key] = !!obj; // obj.ETag;
                                        }
                                    });
                                    const size = Object.keys(keys).length;
                                    progress(size, size, {
                                        desc: `获取已上传文件 ${data.NextMarker || ""}`,
                                        depth: 0,
                                        color: "gray",
                                    });
                                    if (data.NextMarker) {
                                        getParts(data.NextMarker);
                                        return;
                                    }
                                    info("");
                                    cachedFiles = keys;
                                    setCache(
                                        cacheKey,
                                        cachedFiles,
                                        cacheType,
                                        cacheParam,
                                    );
                                    readyForUpload();
                                }

                                function getParts(marker = null) {
                                    cos.getBucket(
                                        {
                                            Bucket: Bucket,
                                            Region: Region,
                                            MaxKeys: 1000,
                                            Marker: marker,
                                            Prefix: prefix || "",
                                        },
                                        (err, data) => {
                                            if (err) {
                                                reject(getPrint(err, true));
                                                return;
                                            }
                                            getPartsComplete(data);
                                        },
                                    );
                                }
                                getParts();
                            } else {
                                readyForUpload();
                            }
                        },
                    );

                    function readyForUpload() {
                        const rawLength = srcs.length;
                        // 过滤文件
                        srcs = srcs.filter((src) => {
                            const cachedValue =
                                cachedFiles[
                                    path
                                        .join(
                                            dst_folder,
                                            path.relative(base, src),
                                        )
                                        .replace(/\\/g, "/")
                                ];
                            switch (strategy) {
                                case "lazy": {
                                    if (cachedValue) {
                                        return false;
                                    }
                                    break;
                                }
                                case "hash": {
                                    const hash = crypto
                                        .createHash("md5")
                                        .update(readFileSync(src))
                                        .digest("hex");
                                    if (cachedValue === hash) {
                                        return false;
                                    }
                                    break;
                                }
                                case "force": {
                                    break;
                                }
                                default: {
                                    throw `Unkown Strategy ${strategy}`;
                                    break;
                                }
                            }
                            return true;
                        });
                        info(`预处理完成!`, "\r");
                        info(
                            `实际 ${srcs.length}, 忽略 ${rawLength - srcs.length}, 共计 ${rawLength}`,
                        );
                        const _from = 0;
                        function uploadRange(from = 0) {
                            const tmpSrcs = srcs.slice(
                                from,
                                from + concurrency,
                            );
                            if (tmpSrcs.length === 0) {
                                resolve(true);
                                return;
                            }
                            let left = tmpSrcs.length;
                            const files = tmpSrcs.map((src, _index) => {
                                const dst = path
                                    .join(dst_folder, path.relative(base, src))
                                    .replace(/\\/g, "/");
                                let speed = 0;
                                return {
                                    Bucket: Bucket,
                                    Region: Region,
                                    Key: dst,
                                    FilePath: src,
                                    onProgress: (progressInfo) => {
                                        speed = progressInfo.speed;
                                        // progress(index + from, srcs.length, {
                                        //     desc: `Uploading to ${dst} ${progressInfo.speed ? format_bytes(progressInfo.speed) + "/s" : ""}`,
                                        //     depth: 0,
                                        //     // format_bytes: true,
                                        //     color: "green",
                                        //     align: true,
                                        // });
                                    },
                                    onFileFinish: (err, _data) => {
                                        if (err) {
                                            // setCache(cacheKey, cachedFiles, cacheType, cacheParam);
                                            // reject(getPrint(err, true));
                                            return;
                                        }
                                        --left;
                                        progress(
                                            from + (tmpSrcs.length - left),
                                            srcs.length,
                                            {
                                                desc: `Uploaded to ${dst} ${format_bytes(speed)}/s`,
                                                depth: 0,
                                                // format_bytes: true,
                                                color: "green",
                                                align: true,
                                            },
                                            true,
                                        );
                                        // 当前上传的，存储为 hash
                                        cachedFiles[dst] = crypto
                                            .createHash("md5")
                                            .update(readFileSync(src))
                                            .digest("hex");
                                        if (left === 0) {
                                            setCache(
                                                cacheKey,
                                                cachedFiles,
                                                cacheType,
                                                cacheParam,
                                            );
                                            uploadRange(from + concurrency);
                                        }
                                    },
                                };
                            });
                            cos.uploadFiles({
                                files: files,
                                onProgress: (_progressInfo) => {},
                                onFileFinish: (err, _data) => {
                                    if (err) {
                                        setCache(
                                            cacheKey,
                                            cachedFiles,
                                            cacheType,
                                            cacheParam,
                                        );
                                        reject(getPrint(err, true));
                                        return;
                                    }
                                },
                            });
                        }

                        uploadRange();
                    }
                    break;
                }
                default: {
                    resolve(false);
                    break;
                }
            }
        });
    }
}

module.exports = {
    TencentCosCommand,
};
